查看原文
其他

关于嵌入式 Qt 最全最棒的教程(万字干货)

嵌入式ARM 2021-01-31

The following article is from ZLG致远电子 Author 致远电子

重要:
本文摘自致远电子出品书籍《嵌入式Linux开发教程》,本教程凝聚了ZLG致远电子嵌入式工程师的心血,任何形式的转载请务必标注此段文字!!!

整理排版:付斌,来源:ZLG致远电子,作者:致远电子

常见的嵌入式Linux图形界面有Qt/Embedded、DirectFB、MicroWindows/NanoX、MiniGUI和OpenGUI等,每个GUI都有各自不同特点和应用场合,在应用编程上也各不相同。

本文将介绍嵌入式Qt的基础编程,从环境搭建入手,然后介绍了qmake工具以及Qt Creator,紧接着给出Qt常见部件编程和范例,最终以一个经典的贪食蛇游戏在Qt上的实现为例,对前面介绍的编程进行综合。

01
Qt和Qt/Embedded

Qt介绍

Qt是一个跨平台应用程序和UI开发框架。使用Qt只需一次性开发应用程序,无需重新编写源代码,便可跨不同桌面和嵌入式操作系统部署这些应用程序。Qt原为奇趣科技公司(Trolltech,www.trolltech.com)开发维护,现在被Nokia公司收购。目前在Nokia的推动下,Qt的发展非常快速,版本不断更新。目前最新的Qt主版本为5.4,所支持的平台如图1所示。

1 Qt支持的平台

注:本文使用的版本为4.7.3,由于qt 5 和qt 4编程方面有些差别,且市面上大多数都是qt4编程的资料,qt5的相对较少,所以建议使用qt4进行编程开发。

Qt/Embedded介绍

嵌入式Linux发行版上的Qt属于Qt的Embedded Linux分支平台(本文简称为Qt/E)。Qt/E在原始Qt的基础上,做了许多出色的调整以适合嵌入式环境。同桌面版的Qt/X11相比,嵌入式的Qt/E很节省内存,因为它不需要X server或是Xlib库,它在底层摒弃了Xlib,采用Framebuffer(帧缓冲)作为底层图形接口。Qt/E的应用程序可以直接写内核帧缓冲,这避免开发者使用繁琐的Xlib/Server系统。

Qt/E所面对的硬件平台较多,当开发人员需要在某硬件平台上移植Qt/E时,需要下载Qt源代码,利用交叉编译器编译出Qt库,接着需要将Qt库复制两份,一份放置在开发主机上,供编译使用,一份放在目标板上,供运行时动态加载使用。具体流程如图2所示。

2 编译Qt库流程图


02
Qt/Embedded交叉编译环境的搭建之环境介绍与安装

环境介绍

主机系统:Ubuntu 12.04 32-bit;
交叉编译环境:arm-none-linux-gnueabi;
开发板:EasyARM-i.MX283A;
安装文件目录结构:/home/vmuser/nfs_shared用于开发板的挂载PC机的路径。

安装tslib1.4

在采用触摸屏的移动终端中,触摸屏性能的调试是一个重要问题之一,因为电磁噪声的缘故,触摸屏容易存在点击不准确,有抖动等问题。

tslib是一个开源程序,能够为触摸屏驱动获得的采样提供诸如滤波、去抖动、校准等功能,通常作为触摸屏驱动的适配层,为上层的应用提供了一个统一的接口。

如果不采用触摸屏,可以不安装该库,跳过这一小节。

1. 准备工作

确保已安装autoconf、automake和libtool。如果没有安装,或者不确定,可输入下列命令进行安装:


注意:确保内核源码目录下的include/linux/input.h的EV_VERSION值与交叉编译工具定义的EV_VERSION值一致(本例为arm-none-linux-gnueabi/libc/usr/include/linux/input.h),不然在开发板上tslib会报告“selected device is not a touchscreen I understand”错误。

2. 下载源码

从网上下载tslib源代码,本文以tslib-1.4.tar.gz为例。下载后,得到tslib-1.4.tar.gz,解压,如下命令:
$ tar -zxvf tslib-1.4.tar.gz

3. 配置

进入解压的目录,执行如下命令:


--prefix指定安装路径,用户可以自行指定tslib的安装目录。
--host指定交叉编译器,如果交叉编译器是arm-none-linux-guneabi-gcc,则指定arm-none-linux-guneabi。

4. 编译

执行make指令:
$ make

5. 安装

$ make install
编译生成的库,头文件等都拷贝到prefix指定的路径中。
如果可以看到该指定的路径下有4个文件夹:/bin、/etc、/lib、/include,则表示安装完成。

6. 修改ts.conf内容

为了在移植开发板的时候,可以制定输入模块,需要修改ts.conf文件的内容。

进入安装目录下的/etc/文件夹,修改ts.conf文件的内容。
$ vi ts.conf

找到#module_raw input那一行,去掉注释#,如下图3所示。

图3 ts.conf文件内容


注意:行首不要留空格,要顶格。

7. 移植到开发板

将安装路径下的tslib整个文件夹,下载到开发板上,本例子放置在开发板的/usr/local/下,如图4所示。

图4 开发板tslib路径

8. 设置开发板环境

通过串口软件(如本文使用的Tera Term),打开开发板的环境变量文件/etc/profile。
# sudo vi /etc/profile

在末尾添加如下内容:


其中TSLIB_ROOT更改为自己实际存放的tslib的绝对路径。

TSLIB_TSDEVICE和QWS_MOUSE_PROTO这两项需要查看自己的开发板触摸屏设备对应/dev/input/下的文件。

9. 执行测试命令

重新启动开发板,使系统重新读取/etc/profile的环境变量,进入tslib/bin目录,执行如下命令:
# cd /usr/local/tslib/bin
# ./ts_calibrate

如果开发板出现如图5所示界面,则至此,tslib的安装和移植已经成功完成。

可以执行该目录下的其他程序,体检触摸屏。

图5 5点触摸屏校准画面

03
Qt/Embedded交叉编译环境的搭建之编译qt4.7.3-arm

下载qt-4.7.3源码包(qt-everywhere-opensource-src-4.7.3.tar.gz),进入源码包的目录,然后解压缩,进入解压缩的目录,配置相应的选项内容保存到脚本build-qt里面,脚本文件内容如图6所示。

图6 build-qt文件

然后在该目录下的mkspec/qws/linux-arm-gnueabi-g++/qmake.conf文件添加-lts参数和在文件末尾添加如下两行:


实际操作后的效果如图7所示。

图7 qmake.conf文件内容

然后开始安装,具体命令如下:


注意:需要指定tslib相关文件的路径如:-I /home/zlg/nfs_shared/tslib/include –L /hone/zlg/nfs_shared/tslib/lib和-qt-mouse-tslib,另外配置之前确保g++已安装。

至于./configure 的选项可以通过./configure -help查看,并参照实际开发板,选择合适的选项。例如选项-prefix <安装路径>指定安装目录。

如果没有指定安装路径则默认安装在/usr/local/Trolltech/QtEmbedded-4.7.3-arm。

配置完成后,开始执行如下命令:
$ make

此处根据配置的选项,可能花费的时间比较久,需要耐心等待。

接下来执行如下命令进行安装:
$ make install

安装成功后,则可以在安装目录下查看到相关文件夹,如本例安装路径为/home/vmuser/nfs_shared/qt-4.7.3-arm,查看到有bin、imports、include、lib、mkspec、plugins、translations。

接下来就要移植到开发板上。

需要将安装目录下的lib和plugins移植到ARM开发板上,本例安装在/usr/local/qt-4.7.3-arm,所以在开发板上执行如下命令:
# mkdir /usr/local/qt-4.7.3-arm

通过NFS,将lib和plugins下载到开发板上。在开发板上执行的命令如下:


设置相应的环境变量,在开发板上执行如下命令:
# vi /etc/profile

然后在文件末尾追加如下内容:


04
Qt Sdk搭建

Qt SDK简介


Qt是一个跨平台的图形框架,在安装了桌面版本的Qt SDK的情况下,用户可以先在PC主机上进行Qt应用程序的开发调试,待应用程序基本成型后,再将其移植到目标板上。

桌面版本的Qt SDK主要包括以下部分:

用于桌面版本的Qt库
集成开发环境IDE(Qt Creator)

Qt SDK安装


桌面版本的Qt SDK支持三个平台:Windows、Linux、Mac。这里只讲述Linux桌面版本的Qt SDK的安装。其他平台下的安装可查阅官方资料。用户可以在Qt官方网站找到三个平台对应的安装包。推荐通过Ubuntu下的apt-get获取Linux版的Qt SDK。在Linux主机可以正常上网的条件下,先进行安装源的更新,否则可能导致Qt-SDK安装失败。安装源更新命令如下:

$ sudo apt-get update

其执行过程如所图8示。

图18 更新Linux安装源

安装源更新成功后,可以使用如下命令获取并安装Qt SDK。

$ sudo apt-get install qt-sdk

在Qt SDK的安装过程中可能会出现如图9所示的警告窗口,这时只需要选中该窗口并按回车键即可。

图9 Qt SDK安装过程中可能出现的警告窗口

当Qt SDK安装完成后,终端显示如图10所示。

图10 完成Qt SDK的安装

安装成功后,会在/usr/bin/目录下产生两个可执行文件qmake和qmake-qt4如图11所示。

图11 qmake可执行文件路径

第1个qmake是qt5版本的,本文使用第2个qmake-qt4可执行文件,为了区别用于嵌入式的qmake指令和桌面版本的qmake指令,可以通过设置别名,如本文用qmake-arm来指定嵌入式版本的qmake,在~/.bashrc文件的末尾添加如下命令:

alias qmake-arm=/home/vmuser/nfs_shared/qt-4.7.3-arm/bin/qmake


添加成功后,可以执行source ~/.bashrc使其立刻生效。然后可以分别执行qmake-qt4–v和qmake-arm–v,查看版本是否分别对应嵌入式和桌面版本,命令如下:


PC终端上显示如图12所示。

图12 qmake的不同版本

至此,Qt SDK的环境搭建已经全部完成,接下来将介绍如何编译Qt应用程序。

Qt可以使用三种方法来编译Qt应用程序:第一种方法是使用Qt提供的qmake工具,第二种方法是使用集成开发环境(IDE),而第三种使用第三方的编译工具。本文主要介绍前两种方法。

05
Qt Creator

Qt Creator配置

Qt Creator是一个强大的跨平台IDE,集编辑,编译,运行,调试功能与一体。其代码编辑器支持关键字高亮,上下文信息提示,自动完成,智能重命名等高级功能。IDE中集成的可视化界面编辑器,可以让用户以所见即所得的方式进行图形程序的设计。其编译,运行无需敲入命令,直接点击按钮或使用快捷键即可完成。同时还支持图形化的调试方式,可以以插入断点,单步运行,追踪变量,查看函数堆栈等方式进行应用程序的调试开发。

通过如下命令启动QtCreator:
$ qtcreator

Qt Creator主界面如图15所示。

图15 Qt Creator主界面

如果已安装了桌面版的Qt和嵌入式版的Qt,则需要设置Qt Creator所使用的Qt版本。点击菜单栏的Tools→Options,将得到如图16所示的界面。

图16 Qt工具-选项界面

点击左边的Build & Run,在右边弹出的Build & Run中,选择Qt Version,手动加入Qt的版本,以加入桌面版的Qt为例:

点击Add,弹出选择qmake可执行文件的窗口,然后选择路径/usr/bin/qmake-qt4的执行文件如图17所示,点击打开。

图17 选择qmake可执行文件窗口

在Version Name一栏输入版本名字,如Destop-qt4,点击Apply如下图18所示。

图18 添加Qt版本的窗口

然后选择Qt Version旁边的Kits,点击Add,输入Name,选择编译器、Qt-version等,配置选项如图19所示,点击Apply,然后点击OK即可。

图19 配置编译工具(Kits)窗口

Qt Creator使用范例

下面将通过一个简单的例程,说明一下如何使用Qt Creator进行程序的开发。

单击Qt Creator主界面的New Project,得到如下图20所示的界面。

图20 创建新项目界面

选择Qt Widgets Application,点击Choose,进入如图21的界面,设置项目名称和路径。接下来可以一路点击下一步,选择默认的方式,直到出现如图22的界面。

图21 项目名称和路径的界面


图22 项目管理界面

单击Finish,将进入如图23界面,Qt Creator已经自动生成一个最基本Qt程序所需的代码,用户可以在此基础上做进一步的开发。

图23 mainwindow.cpp界面

单击侧边栏的Forms中的mainwindow.ui可以启动可视化编辑器,如图24所示。

图24 可视化界面编辑器

在图25的界面中,通过拖动左边控件侧边栏中的控件到程序主界面中,以所见即所得的方式设计程序界面。下面拖动一个QLabel控件到程序主页面上,点击QLabel控件,在右下角栏目中设置QLabel上文字为“Hello Qt!”。如图14.25所示。

图25 hello Qt程序界面

接下来通过左边侧边栏上的Debug,选择前面所设置桌面版本的Qt的Debug。如图26所示。

图26 选择Qt版本

最后,点击“▶”按钮对程序进行编译链接运行。如编译无误,将自动启动应用程序。界面如图14.27所示。

图27 hello Qt界面

06
在嵌入式环境运行Qt程序

将程序编译成嵌入式版本

由于Qt具有良好的可移植性,在桌面版本中编译运行成功的应用程序,一般只需要用交叉编译工具的qmake重新编译,即可在目标板上运行。执行嵌入式的qmake(别名qmake-arm),重新交叉编译,便可获得嵌入式版本的Qt程序。如图28所示:


图28 移植hello Qt

在目标板上运行程序

建议通过nfs挂载PC主机上的目录至目标板上,便于调试开发。具体的nfs挂载方法请查阅6.4章节的内容。下面假设已经将PC上hello目录挂载在目标板上,接下来的操作都在目标板上进行。

在启动hello程序前,需要先设定Qt的鼠标设备,进行如下命令:


# export QWS_MOUSE_PROTO=tslib:/dev/input/event0


上述命令中tslib指定了触摸屏对应的设备文件,这里指定为/dev/input/event0。但是其值并不固定,需要根据实际情况确定。正常情况下,所需的设备文件位于/dev/input目录下。

在命令行下输入如下命令:


# cat /dev/input/event0 | hexdump


点击触摸屏,如果有数据输出,那么对应的设备文件就是所需要的设备文件。

成功设定鼠标设备后,可以执行如下命令启动Qt程序。


# ./hello -qws


-qws指明这个Qt程序同时作为一个窗口服务器运行,在目标板上启动的第一个Qt程序应使用此参数启动。

在本例中,程序启动成功后,界面如图29所示:


图29 hello程序运行界面

Qt帮助文档

由于Qt中包含了许多类和函数,开发人员不可能每一个类都熟记。所以可以使用参考文档查阅每个类和函数的使用方法。

可以使用Qt的帮助浏览器Qt Assistant,它具有强大的查询和索引功能,自身也是由Qt程序构成的。

在Linux命令行终端下,输入如下命令:


$ assitant-qt4


即会弹出如图30所示的界面。


图30 assistant界面


07
Qt编程实战(上)

接下来将要介绍一些功能部件的简单使用方法,然后在最后通过一个经典的小游戏—贪食蛇示例来说明功能部件之间的组合使用。

这些例子都是手动敲代码,然后用桌面版的qmake(即我们设置别名的qmake-qt4指令)进行编译,而不是借助IDE来进行编程。因为这样可以让读者更加明白代码的运作原理。

1 按钮

本例子由一个按钮构成,这个应用程序的源代码与Hello程序的源代码非常相似,quitButton源代码如程序清单2所示。

程序清单2 quitButon.cpp源码

1 /* quitButton.cpp */
2 #include <QApplication> /* 包含类QApplication的定义 */
3 #include <QPushButton> /* 包含类QPushButton的定义 */
4 int main(int argc, char *argv[])
5 {
6 QApplication app(argc, argv);
7 QPushButton *button = new QPushButton("Quit") ; /*创建一个显示为Quit的按钮窗口部件*/
8 button->show(); /* 使按钮可见 */
9 return app.exec();
10 }

第5行创建了一个QApplication对象,用来管理整个应用程序所用到的资源。Qt支持一些命令行参数,所以这个QApplication构造函数需要两个参数,分别是argc和argv。

第8行将应用程序的控制权转移给Qt。此时,程序将进入事件循环状态,程序会等候用户的动作,例如鼠标单击和按键等操作。

新建一个quitButton目录,然后在该目录下添加quitButton.cpp,内容如上述程序清单1.2所示,然后在该目录下执行如下命令进行编译和执行。

$ qmake-qt4 –project
$ qmake-qt4
$ make

PC上执行过程如图31所示。

图31 quitButton编译和执行过程

由于没有为按钮按下的事件提供任何处理。所以无法按下“按钮”之后退出窗口。这个功能可以通过后面将要介绍的信号与槽的机制来实现。

2 标签和文本框

这一节将简单说明一下窗口加入标签和文本框的使用方法。与按钮源代码差不多,只是调用的类不同而已。

本例由三个窗口部件组成:QLabel,QLineEdit和QWidget。QWidget是该应用程序的主窗口。QLabel和QLineEdit会显示在QWidget中,它们都是QWidget窗口部件的子对象。另外,为了将窗口部件摆放整齐,调用了Qt的布局管理器。下一小节将具体介绍布局管理器的使用方法。

程序清单3 account.cpp源代码

1 /* account.cpp */
2 #include <QApplication>
3 #include <QLabel>
4 #include <QLineEdit>
5 #include <QHBoxLayout>
6
7 intmain(int argc, char *argv[])
8 {
9 QApplication app(argc, argv);
10 QWidget *window = new QWidget; /* 创建Qwidget对象,作为主窗口*/
11 window->setWindowTitle("Enter Your account"); /*设置显示在窗口标题栏上的文字*/
12
13 QLabel *accountLabel = new QLabel("Account: ", window); /*创建标签对象,并显示为Accoutn */
14 QLineEdit *accountEdit = new QLineEdit(window); /* 创建文本框对象 */
15
16 QHBoxLayout *layout = new QHBoxLayout; /* 创建水平布局管理器 */
17 layout->addWidget(accountLabel);
18 layout->addWidget(accountEdit);
19
20 window->setLayout(layout); /* window窗口上安装该布局管理*/
21 window->show();
22 return app.exec();
23 }

第13和14行把window对象传递给QLabel和QLineEdit的构造函数,说明这两个窗口部件为window的子对象。

第16到18行使用了一个水平布局管理器对标签和文本框进行布局处理。下一小节将具体介绍布局管理器。

建立一个新目录为account,在该目录下添加account.cpp源代码文件,然后调用如下命令进行编译,然后执行。

$ qmake-qt4 –project
$ qmake-qt4
$ make

PC上执行过程如图32所示。


图32 account编译和运行界面

3 布局管理器

布局管理器是一个能够对窗口部件的尺寸大小和位置进行设置的对象。Qt有三个主要的布局管理器类:

1、QHBoxLayout:在水平方向上排列窗口部件,从左到右;
2、QVBoxLayout:在垂直方向上排列窗口部件,从上到下;
3、QGridLayout:把各个窗口部件排列在一个网格中。

下面编写一个范例来展示一下布局管理器的灵活用途。本例子将把前两小节的功能部件结合起来使用。

程序清单4 layout.cpp

1 /* layout.cpp */
2 #include <QtGui>
3 #include <QApplication>
4
5 int main(int argc, char *argv[])
6 {
7 QApplication app(argc, argv);
8 QWidget *window = new QWidget;
9 window->setWindowTitle("Layout");
10
11 QLabel *accountLabel = new QLabel("Account: ");
12 QLabel *pwLabel = new QLabel("Password: ");
13 QLineEdit *accountEdit = new QLineEdit;
14 QLineEdit *pwEdit = new QLineEdit;
15 QPushButton *quitButton = new QPushButton("Quit");
16 QPushButton *nextButton = new QPushButton("Next");
17
18 QHBoxLayout *topLayout = new QHBoxLayout; /* 创建一个水平布局管理器 */
19 topLayout->addWidget(accountLabel); /* 从左到右放置窗口部件 */
20 topLayout->addWidget(accountEdit);
21
22 QHBoxLayout *downLayout = new QHBoxLayout;
23 downLayout->addWidget(pwLabel);
24 downLayout->addWidget(pwEdit);
25
26 QVBoxLayout *leftLayout = new QVBoxLayout; /* 创建一个垂直布局管理器 */
27 leftLayout->addLayout(topLayout); /* 内嵌布局管理器 */
28 leftLayout->addLayout(downLayout);
29 leftLayout->addStretch(); /* 添加分隔符 */
30
31 QVBoxLayout *rightLayout = new QVBoxLayout; /* 创建一个垂直布局管理器 */
32 rightLayout->addWidget(quitButton); /* 从上到下放置窗口部件 */
33 rightLayout->addWidget(nextButton);
34 rightLayout->addStretch();
35
36 QHBoxLayout *mainLayout = new QHBoxLayout;
37 mainLayout->addLayout(leftLayout);
38 mainLayout->addLayout(rightLayout);
39
40 QObject::connect(quitButton, SIGNAL(clicked()), &app, SLOT(quit()));
41 window->setLayout(mainLayout);
42 window->show();
43 return app.exec();
44 }

第29行和34行添加了分隔符(或者称为伸展器),用它来占据余下的空白区域,这样可以确保如果用户将窗口高度变高,可以确保这些按钮和标签部件可以完全占用它们所在布局的上部空间,不会跟随窗口高度变高而变高。

使用布局管理器摆放这些子窗口部件,布局中既可以包含多个窗口部件,也可以包含其他子布局。通过QHBoxLayout、QVBoxLayout和QGridLayout这三个布局的不同嵌套组合,就可能构建出相当复杂的布局层次。如下图33所示:


图33 Layout层次图

当将子布局对象添加到父布局对象中时(第27、28、37和38行),子布局对象里面的窗口部件就会自动重定义自己的父对象。换言之,当将主布局装到window对象中时(第42行)时,它就会成为window指向的对象的子对象了。于是它的所有子窗口部件都会重定义自己的父对象,从而变成window的子对象。

建立一个新目录为layout,在该目录下添加layout.cpp源代码文件,然后调用如下命令进行编译和执行。

$ qmake-qt4 –project
$ qmake-qt4
$ make

PC上的执行过程如图34所示。


图34 layout编译和执行过程

08
Qt编程实战(中)

信号与槽

信号与槽的机制是Qt编程的一个重要部分。这个机制可以在对象之间彼此并不了解的情况下,将它们的行为联系起来。它跟UNIX的信号不是同一个概念,不可相互混淆。

槽和普通的C++成员函数很像,它们可以是虚函数(virtual),也可被重载(overload),可以是公有的(public),保护的(protected),它们可以像任何c++成员函数一样被调用,可以传递任何类型的参数。不同在于一个槽函数能和一个信号相连接,只要信号发出了,这个槽函数就会自动被调用。信号和槽函数间的链接通过connect实现。Connect函数语法如下:

connect(sender, SIGNAL(signal), receiver, SLOT(slot));

sender和receive是QObject对象(QObject是所有Qt对象的基类)指针,signal和slot是不带参数的函数原型。SIGNAL宏和SLOT宏的作用是把它们转换成字符串。

虽然在之前的小节中,已经使用了信号和槽,但是在实际应用中还需要考虑一些规则:

1)一个信号可以连接到多个槽

connect(slider, SIGNAL(valueChanged(int)), spinBox, SLOT(setValue(int)));
connect(slider, SIGNAL(valueChanged(int)), this, SLOT(updateStatusBar(int)));

将滑块的值改变信号,连接微调框的设置值大小的槽和当前对象的更新状态栏的槽。

当信号发出后,槽函数都会被调用,但是调用的顺序不确定,随机的。

2)多个信号可以连接到一个槽

connect(lcd, SIGNAL(over()), this, SLOT(handleError());
connect(calculator, SIGNAL(divisionError()), this, SLOT(handleError()));

将lcd的over()信号和计算器的divisionError()信号与当前对象的handleError()的槽连接。

任何一个信号发出,槽函数都会被执行。

3)一个信号可以和另一个信号相连

connect(lineEdit, SIGNAL(textChanged(Qstring &)), this, SIGNAL(update(Qstring &)));

将文本框的文本改变信号与当前对象的更新信号相连。

第一个信号发出后,第二个信号也同时发送,除此之外,信号与信号连接上,和信号和槽连接相同。

4)连接可以被删除

disconnect(lcd, SIGNAL(over()), this, SLOT(handleError()));

这个函数很少使用,一个对象删除后,Qt自动删除与这个对象相关的所有连接。


注:信号和槽函数必须有着相同的参数类型,这样信号和槽函数才能成功连接。

如果信号里的参数个数多余槽函数的参数,多余的参数被忽略:

connect(ftp, SIGNAL(rawReply(int, const Qstring &)), this, SLOT(checkError(int)));

如果参数类型不匹配,或者信号和槽不存在,则当应用程序使用debug模式构建后,Qt会在运行期间发出警告。如果信号和槽连接时包含了参数的名字,Qt将会给出警告。

接下来,可以利用信号与槽将前面小节介绍的按钮部件提供退出功能,更新的quitButton.cpp源码如程序清单5所示。

程序清单5更新的quitButton.cpp源码

1 /* quitButton.cpp */
2 #include <QApplication>
3 #include <QPushButton>
4 int main(int argc, char *argv[])
5 {
6 QApplication app(argc, argv);
7 QPushButton *button = new QPushButton("Quit");
8 QObject::connect(button, SIGNAL(clicked()),&app, SLOT(quit()));
9 button->show();
10 return app.exec();
11 }

其实就只是增加了第7行代码,将这个按钮的clicked()信号与QApplication对象的quit()槽连接起来。

再介绍一个简单的示例,说明如何利用信号与槽的机制来同步窗口部件,可以通过操作微调框(spin box)或者滑块(slider)来完成数字输入,源码如程序清单6所示。

程序清单6number.cpp源码

1 /* number.cpp */
2 #include <QApplication>
3 #include <QHBoxLayout>
4 #include <QSlider>
5 #include <QSpinBox>
6
7 int main(int argc, char *argv[])
8 {
9 QApplication app(argc, argv);
10 QWidget *window = new QWidget;
11 window->setWindowTitle("Enter Number");
12
13 QSpinBox *spinBox = new QSpinBox; /* 创建微调框 */
14 QSlider *slider = new QSlider(Qt::Horizontal); /* 创建滑块 */
15 spinBox->setRange(0, 100); /* 设置微调框有效范围 */
16 slider->setRange(0, 100); /* 设置滑块有效范围 */
17
18 QObject::connect(spinBox, SIGNAL(valueChanged(int)),slider, SLOT(setValue(int)));
19 QObject::connect(slider, SIGNAL(valueChanged(int)),spinBox, SLOT(setValue(int)));
20 spinBox->setValue(25); /* 设置微调框的值为25 */
21
22 QHBoxLayout *layout = new QHBoxLayout; /* 创建水平布局管理器 */
23 layout->addWidget(spinBox); /* 从左到右放置部件 */
24 layout->addWidget(slider);
25 window->setLayout(layout);
26
27 window->show();
28
29 return app.exec();
30 }

第18至19行调用了两次QObject::connect函数,这是为了确保能够让微调框和滑块同步,以便它们两个总是可以显示相同的数值。只要有一个窗口部件的值改变了,就会发射它的valueChanged(int)信号,而另一个窗口部件就会用这个新值调用它的setValue(int)槽。

建立一个新目录为number,在该目录下添加number.cpp源代码文件,然后调用qmake命令进行编译,然后执行查看效果,命令如下:

$ qmake-qt4–project
$ qmake-qt4
$ make

本机执行过程如图35所示。


图35 number编译和执行过程

主窗口(MainWindow)

应用程序的主窗口提供了用于构建应用程序用户界面的框架,可以通过子类化QMainWindow创建。

一个主窗口主要由菜单栏,工具栏,状态栏、停靠窗口和中央窗口部件组成,如图36所示。


图36主窗口示意图

最上面是窗口标题栏,用于显示标题和一些按钮,如最小化、最大化、关闭按钮等。接下来是菜单栏,显示菜单,然后是工具栏,用于显示工具条,由于Qt的窗口支持多个工具条的显示,所以你可以在四周显示或者并排显示工具条。工具条的下面是停靠窗口,所谓停靠窗口就像画图的工具箱一样,可以在中央窗口的四周显示。再下来是状态栏,显示一些状态,比如鼠标当前位置等。中间最大的中央窗口部件是主要的工作区。

一个最基本的主窗口可以由以下三个文件组成:main.cpp、mainwindow.h和mainwindow.cpp。

mainwindow.h的源码如程序清单7所示。

程序清单7mainwindow.h源代码

1 /* mainwindow.h */
2 #ifndef MAINWINDOW_H
3 #define MAINWINDOW_H
4
5 #include <QMainWindow> /* 包含对QMainWindow的定义 */
6
7 class MainWindow : public QMainWindow /* 声明MainWindow为QMainWindow的子类*/
8 {
9 Q_OBJECT;
10 public:
11 MainWindow(void);
12 ~MainWindow(void);
13 };
14
15 #endif

第2和3行能够防止对该头文件的多重包含。

第9行对于所有定义了信号和槽的类,在类定义开始处的Q_OBJECT宏是必需的。

第11和12行声明类MainWindow的构造函数和析构函数。

接下来看一下mainwindow.cpp对类MainWindow的实现,源码如程序清单14.8所示。

程序清单8mainwindow.cpp源码

1 /* mainwindow.cpp */
2
3 #include "mainwindow.h" /* 包含mainwindow.h头文件 */
4
5 MainWindow::MainWindow(void)
6 {
7 }
8
9 MainWindow::~MainWindow(void)
10 {
11 }

由于只是创建一个基本的主窗口,不包含其他窗口部件,所以,构造函数和析构函数都没有内容。下一小节将会在里面添加构造菜单栏和工具栏的实现。

最后,看一下main.cpp文件,源码如程序清单9所示。

程序清单9main.cpp源码

1  /* main.cpp */23  #include <QApplication>4  #include "mainwindow.h"56  int main(int argc, char *argv[])7  {8    QApplication app(argc, argv);9    MainWindow main;            /* 创建自定义的MainWindow对象 */10    main.show();              /* 使对象显示 */11    return app.exec();12 }

这就是一个最基本的主窗口的所需要的文件。

建立一个新目录为mainwindow,在该目录下添加mainwindow.h、mainwindow.cpp和main.cpp文件,然后调用qmake-qt4命令进行编译,然后执行查看效果,命令如下:

$ qmake-qt4 –project
$ qmake-qt4
$ make

PC上的运行效果如图37所示。


图37 mainwindow运行界面

08
Qt编程实战(下)

1 菜单栏、工具栏和状态栏

大多数图形用户界面都会提供菜单栏和工具栏,以便用户对那些常用的功能进行快速访问。

Qt通过“动作(Action)”的概念简化了有关菜单和工具栏的编程。一个动作(action)是一个可以添加到任意数量的菜单和工具栏上的项。创建菜单和工具栏主要包括以下步骤:

1、创建并且设置动作;
2、创建菜单并且把动作添加到菜单上;
3、创建工具栏并且把动作添加到工具栏上。

这一小节,将为上一节创建的主窗口添加菜单栏、工具栏和状态栏。

更新的mainwindow.h源码如程序清单14.10所示。

程序清单14.10更新的mainwindow.h源码

1 /* mainwindow.h */
2 #ifndef MAINWINDOW_H
3 #define MAINWINDOW_H
4
5 #include <QMainWindow> /* 包含对QMainWindow的定义 */
6
7 class QAction;
8 class QMenu;
9 class QToolBar;
10 class QLabel;
11
12 class MainWindow : public QMainWindow /* 声明MainWindow为QMainWindow的子类*/
13 {
14 Q_OBJECT;
15 public:
16 MainWindow(void);
17 ~MainWindow(void);
18
19 private:
20 QMenu *fileMenu; /* 文件菜单 */
21 QMenu *helpMenu; /* 帮助菜单 */
22
23 QToolBar *fileToolBar; /* 工具栏 */
24
25 QAction *openAction; /* 打开动作 */
26 QAction *closeAction; /* 关闭动作 */
27 QAction *aboutAction; /* 关于动作 */
28
29 QLabel *statusLabel;
30 };
31
32 #endif

该头文件主要添加一些私有成员的声明,如第20到29行的私有成员变量。

对于这些私有变量,使用了它们的类前置声明(第7到10行)。这样就不用包含与这些类相关的头文件(如<QMenu>、<QLabel>等),可以使编译过程更快一些。

接下来主要看一下更新的mainwindow.cpp源码,具体实现如程序清单14.11所示。

程序清单14.11更新的mainwindow.cpp源代码

1 /* mainwindow.cpp */
2
3 #include <QtGui>
4 #include "mainwindow.h"
5
6 MainWindow::MainWindow(void)
7 {
8 openAction = new QAction(tr("&Open"), this); /* 创建打开动作 */
9 openAction->setStatusTip(tr("Open the file")); /* 设置状态提示 */
10
11 closeAction = new QAction(tr("&Close"), this); /* 创建关闭动作 */
12 closeAction->setStatusTip(tr("Close the file")); /* 设置状态提示 */
13
14 aboutAction = new QAction(tr("&About"), this); /* 创建关于动作 */
15 aboutAction->setStatusTip(tr("About")); /* 设置状态提示 */
16
17 fileMenu = menuBar()->addMenu(tr("&File")); /* 创建file菜单 */
18 fileMenu->addAction(openAction);
19 fileMenu->addAction(closeAction);
20
21 helpMenu = menuBar()->addMenu(tr("&Help")); /* 创建help菜单 */
22 helpMenu->addAction(aboutAction);
23
24 fileToolBar = addToolBar(tr("&File")); /* 创建File工具栏 */
25 fileToolBar->addAction(openAction); /* 添加打开动作到File工具栏*/
26 fileToolBar->addAction(closeAction); /* 添加关闭动作到工具栏 */
27 fileToolBar->addAction(aboutAction); /* 添加关于动作到工具栏 */
28
29 statusLabel = new QLabel;
30 statusLabel->setMinimumSize(statusLabel->sizeHint());
31 statusLabel->setAlignment(Qt::AlignHCenter); /* 设置居中 */
32
33 statusBar()->addWidget(statusLabel); /* 添加状态标签到状态栏 */
34 }
35
36 MainWindow::~MainWindow(void)
37 {
38 }

第8至15分别创建不同的项,在字符串周围的tr函数调用是把它们翻译成其他语言的标记。每个QObject对象以及包含有Q_OBJECT宏的子类中都有这个函数的声明。尽管应用程序并没有要翻译成其他语言的打算,但是在用户可见的字符串周围使用tr()是一个很不错的习惯。

在这些字符串中,使用了“&”来表示快捷键,用户可在支持快捷键的平台下通过按快捷键激活,如本例的按下Alt + O激活Open动作。

在Qt中,菜单都是QMenu的实例。QmainWindow::menuBar函数返回一个指向QMenuBar的指针,菜单栏会在第一次调用menuBar函数的时候就创建出来。第17至19行创建了file菜单后,把Open和Close动作添加进去。通过类似的方法创建help菜单。

创建工具栏与创建菜单的过程很相似,第24至27行创建了file工具栏,添加了Open、Close和About的动作。

状态栏位于主窗口的最下方,用于显示状态提示消息。QMainWindow::statusBar函数返回一个指向状态栏的指针,第一次调用statusBar函数的时候会创建状态栏。状态栏指示器是一些简单的QLabel。

main.cpp文件不用改变,重新调用qmake-qt4命令进行编译,然后执行查看效果,命令如下:

$ qmake-qt4 –project
$ qmake-qt4
$ make

PC上的运行效果如图38所示。


图38更新的mainwindow界面

2 事件

事件(event)是由窗口系统或者Qt自身产生的,用于响应所发生的各类事情。当用户按下或者松开键盘或者鼠标上的按键时,就会产生一个键盘或者鼠标事件。当用户改变窗口的大小的时候也会产生一个绘制窗口的事件。刚才谈到的事件都是对用户的操作做出响应而产生的,还有一些事件是系统独立产生的,如定时器事件。

由于有信号与槽的机制,一般不需要考虑事件,在发生某些重要的事情时,Qt窗口部件都会发射信号。但是当需要编写自定义的窗口部件,或者希望改变已经存在的Qt窗口部件的行为,事件就变得十分有用。

不应该把“事件”和“信号”这两个概念混淆。事件关注的是窗口部件本身的实现,而信号关注的是窗口部件的使用。例如,当使用QPushButton时,用户更多关注该窗口部件是否有被人点击使用,即对它的clicked信号更为关注。而很少关心发射该信号的底层鼠标或者键盘事件。除非是要自定义一个类似QPushButton的类(窗口部件本身),就需要编写一定的处理鼠标和键盘事件的代码。

所有组件的父类QWidget定义了很多事件处理函数,如keyPressEvent函数、keyReleaseEvent函数、muuseDoubleClickEvent函数、mouseMoveEvent函数、mousePressEvent函数和mouseRelease函数等。

下面通过自定义一个标签的子类,当用户点击鼠标、移动鼠标和释放鼠标后,在该窗口部件中显示鼠标当前的坐标。该实例由三个文件组成:main.cpp、coordinate_label.h和coordinate_label.cpp。

coordinate_label.h源码如程序清单12所示。

程序清单12 coordinate_label.h源码

1 /* coordinate_label.h */
2
3 #ifndef COORDINATE_LABEL_H
4 #define COORDINATE_LABEL_H
5
6 #include <QLabel>
7
8 class QMouseEvent;
9
10 class CoordinateLabel : public QLabel /* 自定义QLabel的子类 */
11 {
12 protected:
13void mousePressEvent(QMouseEvent *event); /* 定义鼠标按下事件 */
14void mouseMoveEvent(QMouseEvent *event); /* 定义鼠标移动事件 */
15void mouseReleaseEvent(QMouseEvent *event); /* 定义鼠标释放事件 */
16 };
17
18 #endif

第13至15行声明需要重定义的事件处理函数。具体代码实现在coordinate_label.cpp文件中,源码如程序清单13所示。

程序清单13 coordinate_label.cpp源码

1 /* coordinate_label.cpp */
2
3 #include <QMouseEvent>
4 #include "coordinate_label.h"
5
6 void CoordinateLabel::mousePressEvent(QMouseEvent *event)
7 {
8 this->setText(QString("Mouse Press at:[%1, %2]")
9 .arg(event->x())
10 .arg(event->y()));
11 }
12
13 void CoordinateLabel::mouseMoveEvent(QMouseEvent *event)
14 {
15 this->setText(QString("Mouse Move at:[%1, %2]")
16 .arg(event->x())
17 .arg(event->y()));
18 }
19
20 void CoordinateLabel::mouseReleaseEvent(QMouseEvent *event)
21 {
22 this->setText(QString("Mouse Release at:[%1, %2]")
23 .arg (event->x())
24 .arg(event->y()));
25 }

类QString的具体用法可以查阅帮助文档,在本例中,将自定义的QLabel的子类CoordinateLabel,设置其文本显示为当前鼠标事件的x轴与y轴。

main.cpp的源码如程序清单14所示。

程序清单14main.cpp源码

1 /* main.cpp */
2
3 #include <QApplication>
4 #include "coordinate_label.h"
5
6 int main(int argc, char *argv[])
7 {
8 QApplication app(argc, argv);
9 CoordinateLabel *myLabel = new CoordinateLabel;
10 myLabel->setWindowTitle("Mouse Event Demo"); /* 设置窗口标题 */
11 myLabel->resize(300,200); /* 设置自定义的窗口部件尺寸*/
12 myLabel->show();
13 return app.exec();
14 }

建立一个新目录为coordinate_label,在该目录下添加coordinate_label.h、coordinate_label.cpp和main.cpp文件,然后调用qmake-qt4命令进行编译,然后执行查看效果,命令如下:

$ qmake-qt4 –project
$ qmake-qt4
$ make

PC上的运行效果如图39所示。


图39 coordinateLabel界面

-END-




推荐阅读



【01】在单片机(MCU)上运行Qt?Qt for MCUs 1.0正式发布【02】关于QT,你需要知道这些基础知识【03】嵌入式Linux GUI的新选择:开源!免费!高效!易用!【04】几种常用的嵌入式Linux GUI及其特点【05】嵌入式 Linux QT 编程入门


免责声明:整理文章为传播相关技术,版权归原作者所有,如有侵权,请联系删除

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存